﻿using System;
using System.Linq;
using Complaint.Models.Dtos;
using Complaint.Models.Dtos.Enums;
using Complaint.Repositories.Helpers;

namespace Complaint.Repositories
{
    public class ComplaintRepository : BaseRepository<Models.Dtos.Complaint>, IRepository<Models.Dtos.Complaint>
    {
        #region IRepository<Complaint> Members

        public void Create(Models.Dtos.Complaint dto)
        {
            ObjectContext.AddToComplaint(dto);
            Update(dto);
        }

        public IQueryable<Models.Dtos.Complaint> FindAll()
        {
            return ObjectContext.Complaint;
        }

        public Models.Dtos.Complaint FindById(int id)
        {
            return FindAll().FirstOrDefault(c => c.ComplaintID == id);
        }

        public IQueryable<Models.Dtos.Complaint> FindByName(string name)
        {
            return from complaint in FindAll()
                   where complaint.Title.Contains(name) || complaint.Body.Contains(name)
                   select complaint;
        }

        #endregion

        public IQueryable<Models.Dtos.Complaint> FindAll(int top)
        {
            var result = FindAll();
            result = result.OrderByDescending(o => o.DateLodged).Take(top);
            return result;
        }

        public IQueryable<Models.Dtos.Complaint> FindAll(int pageNumber, int pageSize, out int totalNumber)
        {
            return FindAll(pageNumber, pageSize, out totalNumber, null, null);
        }

        public IQueryable<Models.Dtos.Complaint> FindAll(int pageNumber, int pageSize, out int totalNumber, DateTime? dateFrom, DateTime? dateTo)
        {
            var count = checked(pageNumber - 1) * pageSize;
            var result = FindAll();
            if (dateFrom != null)
                result = result.Where(c => c.DateLodged >= dateFrom);
            if (dateTo != null)
                result = result.Where(c => c.DateLodged <= dateTo);

            totalNumber = result.Count();
            result = result.OrderByDescending(o => o.DateLodged).Skip(count).Take(pageSize);
            return result;
        }

        public IQueryable<Models.Dtos.Complaint> FindAll(LoadComplaint loadOption, int pageNumber, int pageSize, out int totalNumber)
        {
            var result = FindAll(pageNumber, pageSize, out totalNumber);
            result = LoadResult(result, (int)loadOption);

            return result;
        }

        public IQueryable<Models.Dtos.Complaint> FindAll(LoadComplaint loadOption, int pageNumber, int pageSize, out int totalNumber, int categoryId)
        {
            int[] categories = {categoryId};
            return FindAll(loadOption, pageNumber, pageSize, out totalNumber, null, null, categories);
        }

        public IQueryable<Models.Dtos.Complaint> FindAll(LoadComplaint loadOption, int pageNumber, int pageSize, out int totalNumber, DateTime? dateFrom, DateTime? dateTo)
        {
            var result = LoadResult(FindAll(pageNumber, pageSize, out totalNumber, dateFrom, dateTo), (int)loadOption);

            return result;
        }

        public IQueryable<Models.Dtos.Complaint> FindAll(LoadComplaint loadOption, int pageNumber, int pageSize, out int totalNumber, DateTime? dateFrom, DateTime? dateTo, int[] categoryId)
        {
            var count = checked(pageNumber - 1) * pageSize;
            var result = FindAll(loadOption).ApplyCategoryFilter(categoryId);
            if (dateFrom != null)
                result = result.Where(c => c.DateLodged >= dateFrom);
            if (dateTo != null)
                result = result.Where(c => c.DateLodged <= dateTo);

            totalNumber = result.Count();
            result = result.OrderByDescending(o => o.DateLodged).Skip(count).Take(pageSize);
            return result;
        }

        public IQueryable<Models.Dtos.Complaint> FindByUser(int id, LoadComplaint loadOption)
        {
            return from complaint in FindAll(loadOption)
                   where complaint.User.UserID == id
                   select complaint;
        }

        public IQueryable<Models.Dtos.Complaint> FindByUser(int id, LoadComplaint loadOption, int pageNumber, int pageSize, out int totalNumber)
        {
            var count = checked(pageNumber - 1) * pageSize;
            var result = FindByUser(id, loadOption);

            totalNumber = result.Count();
            return result.OrderByDescending(o => o.DateLodged)
                .Skip(count).Take(pageSize);
        }

        public IQueryable<Models.Dtos.Complaint> FindByUser(string name, LoadComplaint loadOption)
        {
            return from complaint in FindAll(loadOption)
                   where complaint.User.DisplayName.Contains(name)
                       || complaint.User.FirstName.Contains(name)
                       || complaint.User.LastName.Contains(name)
                   select complaint;
        }

        public IQueryable<Models.Dtos.Complaint> FindByUser(
            string name, 
            LoadComplaint loadOption,
            DateTime? dateFrom,
            DateTime? dateTo)
        {
            var result = FindByUser(name, loadOption);
            if (dateFrom != null)
                result = result.Where(c => c.DateLodged >= dateFrom);
            if (dateTo != null)
                result = result.Where(c => c.DateLodged <= dateTo);
            return result;
        }

        public IQueryable<Models.Dtos.Complaint> FindByUser(string name, LoadComplaint loadOption, int pageNumber, int pageSize, out int totalNumber)
        {
            var count = checked(pageNumber - 1) * pageSize;
            var result = FindByUser(name, loadOption);

            totalNumber = result.Count();
            return result.OrderByDescending(o => o.DateLodged)
                .Skip(count).Take(pageSize);
        }

        public IQueryable<Models.Dtos.Complaint> FindByOrganisation(int id, LoadComplaint loadOption)
        {
            return from complaint in FindAll(loadOption)
                   where complaint.Organisation.OrganisationID == id
                   select complaint;
        }

        public IQueryable<Models.Dtos.Complaint> FindByOrganisation(int id, LoadComplaint loadOption, int pageNumber, int pageSize, out int totalNumber)
        {
            var count = checked(pageNumber - 1) * pageSize;
            var result = FindByOrganisation(id, loadOption);

            totalNumber = result.Count();
            return result.OrderByDescending(o => o.DateLodged)
                .Skip(count).Take(pageSize);
        }

        public IQueryable<Models.Dtos.Complaint> FindByOrganisation(string name, LoadComplaint loadOption)
        {
            return from complaint in FindAll(loadOption)
                   where complaint.Organisation.Name.Contains(name)
                   || complaint.Organisation.OrganisationAlias.Any(oa => oa.Alias.Contains(name))
                   select complaint;
        }

        public IQueryable<Models.Dtos.Complaint> FindByOrganisation(
            string name, 
            LoadComplaint loadOption,
            DateTime? dateFrom,
            DateTime? dateTo)
        {
            var result = FindByOrganisation(name, loadOption);
            if (dateFrom != null)
                result = result.Where(c => c.DateLodged >= dateFrom);
            if (dateTo != null)
                result = result.Where(c => c.DateLodged <= dateTo);
            return result;
        }

        public IQueryable<Models.Dtos.Complaint> FindByOrganisation(string name, LoadComplaint loadOption, int pageNumber, int pageSize, out int totalNumber)
        {
            var count = checked(pageNumber - 1) * pageSize;
            var result = FindByOrganisation(name, loadOption);

            totalNumber = result.Count();
            return result.OrderByDescending(o => o.DateLodged)
                .Skip(count).Take(pageSize);
        }

        public IQueryable<Models.Dtos.Complaint> FindByKeywords(string keywords, LoadComplaint loadOption)
        {
            var results = FindByName(keywords, loadOption)
                    .Union(FindByUser(keywords, loadOption))
                    .Union(FindByOrganisation(keywords, loadOption));

            return results;
        }

        public IQueryable<Models.Dtos.Complaint> FindByKeywords(string keywords, LoadComplaint loadOption, int pageNumber, int pageSize, out int totalNumber)
        {
            var count = checked(pageNumber - 1) * pageSize;
            var results = FindByKeywords(keywords, loadOption);

            totalNumber = results.Count();
            results = results.OrderByDescending(o => o.DateLodged)
                    .Skip(count).Take(pageSize);

            return results;
        }

        public IQueryable<Models.Dtos.Complaint> FindByKeywords(
            string keywords, 
            LoadComplaint loadOption, 
            int pageNumber, 
            int pageSize,
            out int totalNumber,
            DateTime? dateFrom,
            DateTime? dateTo)
        {
            var results = FindByName(keywords, loadOption, dateFrom, dateTo)
                    .Union(FindByUser(keywords, loadOption, dateFrom, dateTo))
                    .Union(FindByOrganisation(keywords, loadOption, dateFrom, dateTo));

            var count = checked(pageNumber - 1) * pageSize;
            totalNumber = results.Count();
            results = results.OrderByDescending(o => o.DateLodged)
                    .Skip(count).Take(pageSize);

            return results;
        }

        public IQueryable<Models.Dtos.Complaint> FindByKeywords(
            string keywords,
            LoadComplaint loadOption,
            int pageNumber,
            int pageSize,
            out int totalNumber,
            DateTime? dateFrom,
            DateTime? dateTo,
            int[] categoryId)
        {
            var results = FindByName(keywords, loadOption, dateFrom, dateTo).ApplyCategoryFilter(categoryId)
                    .Union(FindByUser(keywords, loadOption, dateFrom, dateTo).ApplyCategoryFilter(categoryId))
                    .Union(FindByOrganisation(keywords, loadOption, dateFrom, dateTo).ApplyCategoryFilter(categoryId));

            var count = checked(pageNumber - 1) * pageSize;
            totalNumber = results.Count();
            results = results.OrderByDescending(o => o.DateLodged)
                    .Skip(count).Take(pageSize);

            return results;
        }


        public IQueryable<Models.Dtos.Complaint> FindAll(LoadComplaint loadOption)
        {
            var result = LoadResult(FindAll(), (int)loadOption);

            return result;
        }

        public Models.Dtos.Complaint FindById(int id, LoadComplaint loadOption)
        {
            var result = ObjectContext.Complaint.Where(c => c.ComplaintID == id);

            return LoadResult(result, (int)loadOption).FirstOrDefault();
        }

        public IQueryable<Models.Dtos.Complaint> FindByName(string name, LoadComplaint loadOption)
        {
            var result = LoadResult(FindByName(name), (int)loadOption);

            return result;
        }

        public IQueryable<Models.Dtos.Complaint> FindByName(
            string name, 
            LoadComplaint loadOption,
            DateTime? dateFrom,
            DateTime? dateTo)
        {
            var result = FindByName(name);
            if (dateFrom != null)
                result = result.Where(c => c.DateLodged >= dateFrom);
            if (dateTo != null)
                result = result.Where(c => c.DateLodged <= dateTo);
            result = LoadResult(result, (int)loadOption);

            return result;
        }

        #region override
        protected override IQueryable<Models.Dtos.Complaint> LoadResult(IQueryable<Models.Dtos.Complaint> result, int loadOption)
        {
            if ((loadOption & (int)LoadComplaint.Organisation) == (int)LoadComplaint.Organisation)
            {
                result = result.Include("Organisation");
            }
            if ((loadOption & (int)LoadComplaint.Response) == (int)LoadComplaint.Response)
            {
                result = result.Include("Response");
            }
            if ((loadOption & (int)LoadComplaint.Settlement) == (int)LoadComplaint.Settlement)
            {
                result = result.Include("Settlement.SettlementType");
            }
            if ((loadOption & (int)LoadComplaint.User) == (int)LoadComplaint.User)
            {
                result = result.Include("User");
            }
            if ((loadOption & (int)LoadComplaint.Comment) == (int)LoadComplaint.Comment)
            {
                result = result.Include("Comment");
            }
            return result;
        }
        #endregion

    }
}